/*
  Copyright 2008 Google Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <algorithm>
#include <climits>
#include <cstring>
#include <ctype.h>
#include "string_utils.h"
#include "common.h"

namespace csconnect {

std::string TrimString(const std::string &s) {
  std::string::size_type start = s.find_first_not_of(" \t\r\n");
  std::string::size_type end = s.find_last_not_of(" \t\r\n");
  if (start == std::string::npos)
    return std::string("");

  ASSERT(end != std::string::npos);
  return std::string(s, start, end - start + 1);
}

std::string ToLower(const std::string &s) {
  std::string result(s);
  std::transform(result.begin(), result.end(), result.begin(), ::tolower);
  return result;
}

std::string ToUpper(const std::string &s) {
  std::string result(s);
  std::transform(result.begin(), result.end(), result.begin(), ::toupper);
  return result;
}

void StringAppendVPrintf(std::string *dst, const char* format, va_list ap) {
  // First try with a small fixed size buffer
  char space[1024];

  // It's possible for methods that use a va_list to invalidate
  // the data in it upon use.  The fix is to make a copy
  // of the structure before using it and use that copy instead.
  va_list backup_ap;
#ifdef va_copy
  va_copy(backup_ap, ap);
#else
  backup_ap = ap;
#endif

  int result = vsnprintf(space, sizeof(space), format, backup_ap);
  va_end(backup_ap);

  if (result >= 0 && result < static_cast<int>(sizeof(space))) {
    // It fits.
    dst->append(space);
  } else {
    // Repeatedly increase buffer size until it fits
    int length = sizeof(space);
    while (true) {
      if (result < 0) {
        // Older behavior: just try doubling the buffer size.
        length *= 2;
      } else {
        // We need exactly result + 1 characters.
        length = result + 1;
      }
      char* buf = new char[length];

      // Restore the va_list before we use it again
#ifdef va_copy
      va_copy(backup_ap, ap);
#else
      backup_ap = ap;
#endif
      result = vsnprintf(buf, length, format, backup_ap);
      va_end(backup_ap);

      if ((result >= 0) && (result < length)) {
        // It fits.
        dst->append(buf);
        delete[] buf;
        break;
      }
      delete[] buf;
    }
  }
}

std::string StringPrintf(const char *format, ...) {
  std::string dst;
  va_list ap;
  va_start(ap, format);
  StringAppendVPrintf(&dst, format, ap);
  va_end(ap);
  return dst;
}

std::string StringVPrintf(const char *format, va_list ap) {
  std::string dst;
  StringAppendVPrintf(&dst, format, ap);
  return dst;
}

void StringAppendPrintf(std::string *string, const char *format, ...) {
  va_list ap;
  va_start(ap, format);
  StringAppendVPrintf(string, format, ap);
  va_end(ap);
}

std::string EncodeJavaScriptString(const UTF16Char *source, char quote) {
  ASSERT(source);
  ASSERT(quote == '"' || quote == '\'');

  std::string dest(1, quote);
  for (const UTF16Char *p = source; *p; p++) {
    if (*p == quote) {
      dest += "\\";
      dest += quote;
    } else {
      switch (*p) {
        // The following special chars are not so complete, but also works.
        case '\\': dest += "\\\\"; break;
        case '\b': dest += "\\b"; break;
        case '\f': dest += "\\f"; break;
        case '\n': dest += "\\n"; break;
        case '\r': dest += "\\r"; break;
        case '\t': dest += "\\t"; break;
        case '\v': dest += "\\v"; break;
        default:
          if (*p >= 0x7f || *p < 0x20) {
            char buf[10];
            snprintf(buf, sizeof(buf), "\\u%04X", *p);
            dest += buf;
          } else {
            dest += static_cast<char>(*p);
          }
          break;
      }
    }
  }
  dest += quote;
  return dest;
}

bool DecodeJavaScriptString(const char *source, UTF16String *dest) {
  ASSERT(dest);
  dest->clear();
  if (!source || !*source)
    return false;
  char quote = *source++;
  if (quote != '"' && quote != '\'')
    return false;

  while (true) {
    char c = *source++;
    if (c == quote)
      break;
    if (c == '\0' || c == '\n' || c == '\r') {
      // Unterminated string literal.
      return false;
    }
    if (c == '\\') {
      c = *source++;
      switch (c) {
        case 'b': *dest += '\b'; break;
        case 'f': *dest += '\f'; break;
        case 'n': *dest += '\n'; break;
        case 'r': *dest += '\r'; break;
        case 't': *dest += '\t'; break;
        case 'v': *dest += '\v'; break;
        case 'u': {
          UTF16Char unichar = 0;
          for (int i = 0; i < 4; i++) {
            int ch = *source++;
            if (ch >= '0' && ch <= '9') ch -= '0';
            else if (ch >= 'A' && ch <= 'F') ch = ch - 'A' + 10;
            else if (ch >= 'a' && ch <= 'f') ch = ch - 'a' + 10;
            else return false;
            unichar = static_cast<UTF16Char>((unichar << 4) + ch);
          }
          *dest += unichar;
          break;
        }
        case '\0': return false;
        default: *dest += c; break;
      }
    } else {
      *dest += c;
    }
  }
  return true;
}

std::string EncodeJavaScriptString(const std::string &source, char quote) {
  UTF16String utf16;
  ConvertStringUTF8ToUTF16(source, &utf16);
  return EncodeJavaScriptString(utf16.c_str(), quote);
}

bool DecodeJavaScriptString(const char *source, std::string *dest) {
  ASSERT(dest);
  dest->clear();
  UTF16String utf16;
  if (!DecodeJavaScriptString(source, &utf16))
    return false;
  return ConvertStringUTF16ToUTF8(utf16, dest) == utf16.size();
}

static const char kDatePrefix[] = "\"\\/Date(";
static const char kDatePrefixReplace[] = "new Date(";
static const char kDatePostfix[] = ")\\/\"";
static const char kDatePostfixReplace[] = ")";

bool ConvertJSONToJavaScript(const char *json, std::string *script) {
  if (!json || !json[0]) return false;

  // Valid chars in state 0.
  // Our JSON decoding is stricter than the standard, according to the
  // format we are outputing.
  static const char *kValidChars = ",:{}[]0123456789.-+eE ";
  // State: 0: normal; 1: in word; 2: in string.
  int state = 0;
  const char *word_start = json;
  for (const char *p = json; *p; p++) {
    switch (state) {
      case 0:
        if (*p >= 'a' && *p <= 'z') {
          word_start = p;
          state = 1;
        } else if (*p == '"') {
          state = 2;
        } else if (strchr(kValidChars, *p) == NULL) {
          // Invalid JSON format.
          return false;
        }
        break;
      case 1:
        if (*p < 'a' || *p > 'z') {
          state = 0;
          if (strncmp("true", word_start, 4) != 0 &&
              strncmp("false", word_start, 5) != 0 &&
              strncmp("null", word_start, 4) != 0)
            return false;
        }
        break;
      case 2:
        if (*p == '\\')
          p++;  // Omit the next char. Also works for \x, \" and \uXXXX cases.
        else if (*p == '\n' || *p == '\r')
          return false;
        else if (*p == '"')
          state = 0;
        break;
      default:
        break;
    }
  }

  // Add '()' around the expression to avoid ambiguity of '{}'.
  // See http://www.json.org/json.js.
  std::string json_script(1, '(');
  json_script += json;
  json_script += ')';

  // Now change all "\/Date(.......)\/" into new Date(.......).
  std::string::size_type pos = 0;
  while (pos != std::string::npos) {
    pos = json_script.find(kDatePrefix, pos);
    if (pos != std::string::npos) {
      json_script.replace(pos, arraysize(kDatePrefix) - 1, kDatePrefixReplace);
      pos += arraysize(kDatePrefixReplace) - 1;

      while (json_script[pos] >= '0' && json_script[pos] <= '9')
        pos++;
      if (strncmp(kDatePostfix, json_script.c_str() + pos,
                  arraysize(kDatePostfix) - 1) != 0)
        return false;
      json_script.replace(pos, arraysize(kDatePostfix) - 1,
                          kDatePostfixReplace);
      pos += arraysize(kDatePostfixReplace) - 1;
    }
  }
  *script = json_script;
  return true;
}

bool StartWith(const char *string, const char *prefix) {
  if (string && prefix) {
    size_t string_len = strlen(string);
    size_t prefix_len = strlen(prefix);
    if (string_len >= prefix_len)
      return strncmp(string, prefix, prefix_len) == 0;
  }
  return false;
}

bool StartWithNoCase(const char *string, const char *prefix) {
  if (string && prefix) {
    size_t string_len = strlen(string);
    size_t prefix_len = strlen(prefix);
    if (string_len >= prefix_len)
      return strncasecmp(string, prefix, prefix_len) == 0;
  }
  return false;
}

bool EndWith(const char *string, const char *suffix) {
  if (string && suffix) {
    size_t string_len = strlen(string);
    size_t suffix_len = strlen(suffix);
    if (string_len >= suffix_len)
      return strcmp(string + string_len - suffix_len, suffix) == 0;
  }
  return false;
}

bool EndWithNoCase(const char *string, const char *suffix) {
  if (string && suffix) {
    size_t string_len = strlen(string);
    size_t suffix_len = strlen(suffix);
    if (string_len >= suffix_len)
      return strcasecmp(string + string_len - suffix_len, suffix) == 0;
  }
  return false;
}

}  // namespace csconnect
